﻿// Jinx AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;

namespace Core.Json
{
    public static class Serializer
    {
        public static void ToJson(this object Obj, TextWriter Writer)
        {
            using (Writer)
                WriteObject(Obj, Writer);
        }

        public static string ToJson(this object Obj)
        {
            var Writer = new StringWriter();
            Obj.ToJson(Writer);
            return Writer.ToString();
        }

        public static void WriteJson(this object Obj, TextWriter Writer)
        {
            var Done = WriteValue(Obj, Writer) || WriteArray(Obj, Writer);

            if (!Done)
                WriteObject(Obj, Writer);
        }

        private static bool WriteArray(object Obj, TextWriter Writer)
        {
            if (Obj is IEnumerable)
            {
                if (IsDictionary(Obj)) return false;

                Writer.Write('[');

                var Seq = (Obj as IEnumerable).Cast<object>();
                WriteEnumeration(Writer, Seq, i => WriteObjectOrNull(Writer, i));

                Writer.Write(']');

                return true;
            }

            return false;
        }

        private static bool IsDictionary(object Obj)
        {
            var T = Obj.GetType();

            var DictType = typeof(IDictionary<,>);
            var DictInt = T.GetInterfaces().FirstOrDefault(I => I.IsGenericType
                                                                && I.GetGenericTypeDefinition() == DictType);

            if (DictInt != null)
                return (DictInt.GetGenericArguments()[0] == typeof(string));

            return false;
        }

        private static void WriteEnumeration<T>(TextWriter Writer, IEnumerable<T> E, Action<T> Action)
        {
            var First = true;
            foreach (var i in E)
            {
                if (First)
                    First = false;
                else
                    Writer.Write(',');

                Action(i);
            }
        }

        private static void WriteObject(this object Obj, TextWriter Writer)
        {
            IEnumerable<Member> Members;

            var Dict = Obj as IDictionary;
            if (IsDictionary(Obj) && Dict != null)
                Members = DictionaryMembers(Dict);
            else
                Members = Obj.Members();

            Writer.Write('{');

            WriteEnumeration(Writer, Members, Member =>
                                                  {
                                                      WriteValue(Member.Name, Writer);
                                                      Writer.Write(':');
                                                      WriteObjectOrNull(Writer, Member.Value);
                                                  });

            Writer.Write('}');
        }

        private static IEnumerable<Member> DictionaryMembers(IEnumerable Dict)
        {
            foreach (var Entry in Dict)
                yield return new Member(Entry.Get("Key").ToString(), Entry.Get("Value"));
        }

        private static void WriteObjectOrNull(TextWriter Writer, object O)
        {
            if (O == null)
                Writer.Write("null");
            else
                WriteJson(O, Writer);
        }

        private static bool WriteValue(object Obj, TextWriter Writer)
        {
            return WritePrimitive(Obj, Writer)
                   || WriteString(Obj, Writer)
                   || WriteDecimal(Obj, Writer);
        }

        private static bool WriteDecimal(object Obj, TextWriter Writer)
        {
            var Dec = Obj is decimal;

            if (Dec) Writer.Write(Obj.ToString());

            return Dec;
        }

        private static bool WritePrimitive(object Obj, TextWriter Writer)
        {
            var P = Obj.GetType().IsPrimitive;

            if (P) Writer.Write(Obj.ToString().ToLower());

            return P;
        }

        private static bool WriteString(object Obj, TextWriter Writer)
        {
            var Type = Obj.GetType();

            if (!(Type == typeof(char) || Type == typeof(string)))
                return false;

            Writer.Write('"');

            foreach (var c in Obj.ToString())
            {
                if (char.IsControl(c))
                {
                    if (c == '\b')
                        Writer.Write(@"\b");
                    else if (c == '\f')
                        Writer.Write(@"\f");
                    else if (c == '\n')
                        Writer.Write(@"\n");
                    else if (c == '\r')
                        Writer.Write(@"\r");
                    else if (c == '\t')
                        Writer.Write(@"\t");
                    else
                        Writer.Write(@"\u{0:0000}", (ushort)c);
                }
                else
                {
                    if (c == '"' || c == '\\')
                        Writer.Write('\\');
                    Writer.Write(c);
                }
            }

            Writer.Write('"');

            return true;
        }
    }
}